#!/usr/bin/perl -w

package perlmod::camera;
#perl module for gphoto2 communication

#bi-directional process communication
use IPC::Open2;

#threading support
use threads;
use Thread::Queue;

#polling support
use IO::Select;

use Data::Dumper;


sub new {
	
	$self = {};
	
	#add our shotlist queue
	$self->{'shotlist'} = Thread::Queue->new();
	
	bless($self);
	return $self;
	
};



#connect to gphoto2
sub connect {
	
	print "connect: " . Dumper(@_) . "\n";
	
	#collect our storage
	my $self = shift;
	
	#create filehandles
	my $outputfh;
	my $inputfh;
	
	#open the process
	my $pid = open2( $outputfh , $inputfh , 'sudo gphoto2 --force-overwrite --shell' );
	
	#create the select bitvector
	my $selectobj = IO::Select->new( $outputfh );
	
	#create queues
	$outputqueue = Thread::Queue->new();
	$inputqueue = Thread::Queue->new();
	
	$self->{'ipc'} = {};
	$self->{'ipc'}->{'outputfh'} = $outputfh;
	$self->{'ipc'}->{'inputfh'} = $inputfh;
	$self->{'ipc'}->{'pid'} = $pid;
	$self->{'ipc'}->{'selectobj'} = $selectobj;
	$self->{'ipc'}->{'outputqueue'} = $outputqueue;
	$self->{'ipc'}->{'inputqueue'} = $inputqueue;
	$self->{'ipc'}->{'listenmode'} = 1; # 0 = no automatic wait response, 1 = automatic wait response (to gphoto command prompt)
	
	#create threads
	createreceiverthread($self);
	
	
}


#receiving thread
sub createreceiverthread {
	
	#get self
	my $self = shift;
	
	
	
	print "creating thread\n";
	
	$self->{threads}->{output} = async{
		
		print "Background gphoto2 thread running\n";

		#dereference variables
		my $buffer = '';
		my $charbuf = '';
		my $charsread = '';
		my $timeout = 1;
		my $selectobj = $self->{'ipc'}->{'selectobj'};
		my $outputfh = $self->{'ipc'}->{'outputfh'};
		my $outputqueue = $self->{'ipc'}->{'outputqueue'};
		
		#lock into a closed loop
		while (1) {
			
			
			#check if we can read a byte from output
			while (@readyhandles = $selectobj->can_read($timeout) ) {
				
				#pipe is ready for reading
				
				#read byte from the pipe
				
				#empty out the char buffer
				$charbuf = '';
				
				#while we have something to read
				while ($charsread = read( $outputfh , $charbuf , 1 )) {
					
					#if it's an error, exit.
					if ( !defined $charsread ) {
						print "charsread is undef\n";
						threads->exit();
					}
					
					#add it to the buffer
					$buffer = $buffer . $charbuf;
					
					#print $buffer . "\n";
					
					#check if it is a newline
					if ( $charbuf eq "\n" ) {
						
						#recognise wait-event-and-download commands
						if ( $buffer =~ /^wait-event/ ) {
							
							#our command response, safe to discard
							$buffer = '';
							next;
							
						}
						
						#recognise wait-event response
						if ( $buffer =~ /^Waiting for [0-9]+ events from camera./ ) {
							
							#gphoto response, safe to discard
							$buffer = '';
							next;
							
						}
						
						#recognise unknown ptp response
						if ( $buffer =~ /^UNKNOWN PTP/ ) {
							
							#gphoto response, safe to discard
							$buffer = '';
							next;
							
						}
						
						#recognise FILEADDED event
						if ( ( $buffer =~ /^FILEADDED/ ) || ( $buffer =~ /^Saving file as / ) || ( $buffer =~ /^New file is in location/ ) ) {
							
							#file has been saved to camera, ready for next shot.
							print "queueing next shot\n";
							
							#check if we have any shotlist left
							if ( $self->{'shotlist'}->pending() ) {
								
								#get the array of shot commands
								my $shot = $self->{'shotlist'}->dequeue();
								
								#queue them into the write queue
								foreach $command ( @{$shot} ) {
									
									$self->{'ipc'}->{'inputqueue'}->enqueue( $command );
									print "enqueued $command";
									
								}
								
							}
							
							#and enqueue an output event
							$self->{'ipc'}->{'outputqueue'}->enqueue( $buffer );
							
							#and clear the buffer
							$buffer = '';
							
							#at this point we have nothing left to do, so carry on.
							next;
						}
						
						
						#we have a whole response so enqueue it
						$outputqueue->enqueue($buffer);
						
						#now clear the buffer
						$buffer = '';
						
						#and loop
						next;
					}
					
					#if it is not a newline
					
					#check if it is the command prompt
					if ( $buffer =~ /^gphoto2: \{.*?\} \/> $/ ) {
						
						#we have a command prompt, we can discard it
						$buffer = '';
						
						#send a command if one exists in the buffer
						my $inputqueue = $self->{'ipc'}->{'inputqueue'};
						my $inputfh = $self->{'ipc'}->{'inputfh'};
						
						if ( $inputqueue->pending() ) {
							
							#we have some input queued.
							my $command = $inputqueue->dequeue_nb();
							
							#send the command
							print $inputfh $command;
							print "sending: $command";
							
							#and loop around for a response
							next;
							
						}
						
						
						#and enqueue a ready signal if requested
						if ( $self->{'ipc'}->{'listenmode'} == 0 ) {
							
							$outputqueue->enqueue("command prompt\n");
							
						}
						
						#and send a wait event if requested
						if ( $self->{'ipc'}->{'listenmode'} == 1 ) {
							my $inputfh = $self->{'ipc'}->{'inputfh'};
							print $inputfh "wait-event 1\n";
						}
						
						#and loop
						next;
						
					}
				
				}				
				#if it is anything else, do nothing and loop for the next byte
			}
			
			#end of closed loop
			
		}
		
	};
	
	
}






#disconnect from gphoto2

sub disconnect {
	
	my $self = shift;
	
	my $inputfh = $self->{'ipc'}->{'inputfh'};
	my $outputfh = $self->{'ipc'}->{'outputfh'};
	my $outputqueue = $self->{'ipc'}->{'outputqueue'};
	
	print $outputfh "quit\n";
	close($outputfh);
	close($inputfh);
	$self->{'threads'}->{'output'}->detach();
	
	$outputqueue->enqueue("process closed\n");
	
	
}

#write to camera buffer
sub writetocamera {
	
	my $self = shift;
	
	my $arg = shift;
	
	$self->{'ipc'}->{'inputqueue'}->enqueue($arg);
	
	return;
	
}


#get a refetrence to a shotlist queue
sub getshotlist {
	
	my $self = #shift;
	
	return $self->{'shotlist'};

}


#start the shotlist
sub beginshotlist {
	
	my $self = shift;
	
	if ( $self->{'shotlist'}->pending() ) {
		
		my $shot = $self->{'shotlist'}->dequeue();
		
		foreach $command ( @{$shot} ) {
			
			$self->{'ipc'}->{'inputqueue'}->enqueue( $command );
			print "starting with: $command\n";
			
		}
		
	}
	
}





1;